package com.supplychain.scm.core.task.strategy;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.supplychain.scm.core.basis.constant.StringPool;
import com.supplychain.scm.core.basis.enums.ProductInfoEnum;
import com.supplychain.scm.core.task.AbstractTaskStrategy;
import com.supplychain.scm.core.task.bean.TaskAsyncParam;
import com.supplychain.scm.core.task.bean.TaskAsyncResultTO;
import com.supplychain.scm.core.task.bean.importtask.TaskImport;
import com.supplychain.scm.core.task.bean.importtask.TaskImportBO;
import com.supplychain.scm.core.task.constants.ExcelConstants;
import com.supplychain.scm.core.task.enums.TaskTypeEnum;
import com.supplychain.scm.core.task.util.EasyExcelUtil;
import com.supplychain.scm.core.basis.constant.CommonConstants;
import com.supplychain.scm.core.oss.model.DbyFile;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.util.Assert;

import javax.validation.Valid;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
 * @author liyang
 * @version 1.0.0
 * @ClassName AbstractExcelAnalysisStrategy
 * @Description Excel 解析抽象类
 * @createTime 10:25:00 2023/04/01
 */
@Slf4j
public abstract class AbstractExcelAnalysisStrategy<Model> extends AbstractTaskStrategy<TaskAsyncParam, TaskAsyncResultTO> {

	@Override
	protected final TaskTypeEnum taskType() { return TaskTypeEnum.IMPORT_TASK; }

	/**
	 * 解析数据校验
	 * @param excelList
	 * @param asyncResult
	 * @param excelWriter
	 * @param writeSheet
	 */
	protected void checkExcel(List<Model> excelList, TaskAsyncResultTO asyncResult, ExcelWriter excelWriter, WriteSheet writeSheet) { }

	/**
	 * 解析数据处理
	 * @param excelList
	 * @param asyncResult
	 * @param excelWriter
	 * @param writeSheet
	 * @return Long： 成功数
	 */
	protected abstract Long excelHandler(List<Model> excelList, TaskAsyncResultTO asyncResult, ExcelWriter excelWriter, WriteSheet writeSheet);

	/**
	 * 拆分任务
	 * @param asyncParam
	 * @return
	 */
	@Override
	public TaskAsyncResultTO splitTaskHandler(TaskAsyncParam asyncParam) {
		TaskImport importTask = JSON.parseObject(asyncParam.getTaskParam(), TaskImport.class);
		return EasyExcelUtil.splitTaskHandler(importTask.getImportFileUrl(), importTask.getSheetName(), importTask.getProductInfoEnum(), asyncParam.getPageSize(), this.modelClass());
	}

	@Override
	public final TaskAsyncResultTO taskAsyncHandler(TaskAsyncParam asyncParam) throws IOException {
		TaskAsyncResultTO handler = null;
		try {
			handler = this.importData(asyncParam);
		} catch (Exception e) {
			this.exceptionHandler(asyncParam, handler);
			log.warn("策略处理器发生异常:{}", ExceptionUtils.getStackTrace(e));
			throw e;
		} finally {
			this.finallyHandler(asyncParam, handler);
		}
		return handler;
	}

	protected TaskAsyncResultTO importData(@Valid TaskAsyncParam asyncParam) throws IOException {
		Class<Model> modelClass = this.modelClass();
		TaskImport importTask = JSON.parseObject(asyncParam.getTaskParam(), TaskImport.class);
		TaskImportBO taskImport = new TaskImportBO();
		taskImport.setOperator(importTask.getOperator());
		TaskAsyncResultTO taskAsyncResult = TaskAsyncResultTO.builder().taskResult(JSON.toJSONString(taskImport)).build();

		//解析上传excel
		String fileName = ExcelConstants.DOWNLOAD_LOCAL_DEFAULT_DIR.concat(importTask.getSheetName()).concat(StringPool.UNDERSCORE).concat(UUID.randomUUID().toString()).concat(ExcelConstants.DOT_FILE_TYPE_EXCEL);


		ExcelWriter excelWriter = EasyExcel.write(fileName, modelClass).build();
		WriteSheet writeSheet = EasyExcel.writerSheet(importTask.getSheetName()).build();
		AnalysisEventListener<Model> listener = EasyExcelUtil.getListener(list -> this.excelAppend(list, taskAsyncResult, excelWriter, writeSheet), importTask.getLimitCount());
		EasyExcelFactory.read(this.getInputStream(importTask.getImportFileUrl()), modelClass, listener).sheet().headRowNumber(1).doRead();
		excelWriter.finish();
		DbyFile dbyFile = this.putFile(fileName, importTask.getProductInfoEnum());

		taskAsyncResult.setTaskResult(JSON.toJSONString(TaskImportBO.builder().fileName(importTask.getFileName()).fileUrl(dbyFile.getLink()).importFileUrl(importTask.getImportFileUrl()).build()));
		return taskAsyncResult;
	}

	private void excelAppend(List<Model> excelList, TaskAsyncResultTO taskAsyncResult, ExcelWriter excelWriter, WriteSheet writeSheet){
		this.checkExcel(excelList, taskAsyncResult, excelWriter, writeSheet);
		Long addSize = this.excelHandler(excelList, taskAsyncResult, excelWriter, writeSheet);
		Integer successCount = Objects.nonNull(taskAsyncResult.getSuccessTaskCount()) ? taskAsyncResult.getSuccessTaskCount() : CommonConstants.ZERO;
		Integer failCount = Objects.nonNull(taskAsyncResult.getFailTaskCount()) ? taskAsyncResult.getFailTaskCount() : CommonConstants.ZERO;
		taskAsyncResult.setSuccessTaskCount(successCount + addSize.intValue());
		taskAsyncResult.setFailTaskCount(failCount + excelList.size() - addSize.intValue());
	}

	private Class<Model> modelClass() {
		Class<Model> clazz = null;
		Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
		for (Type type : types) {
			Class<Model> modelClass = (Class<Model>) type;
			Class<? super Model> superclass = modelClass.getSuperclass();
			if (Objects.nonNull(superclass)) {
				clazz = modelClass;
				break;
			}
		}
		Assert.notNull(clazz, "undefined generics");
		return clazz;
	}

	private InputStream getInputStream(String fileUrl) {
		return EasyExcelUtil.getInputStream(fileUrl);
	}

	/**
	 * 上传
	 * @param fileName
	 * @param productInfoEnum
	 * @return
	 * @throws Exception
	 */
	protected DbyFile putFile(String fileName, ProductInfoEnum productInfoEnum) throws IOException {
		return EasyExcelUtil.putFile(fileName,productInfoEnum);
	}

	@Override
	protected final Object beforeHandler(TaskAsyncParam asyncParam) {
		return super.beforeHandler(asyncParam);
	}

	@Override
	protected final TaskAsyncResultTO handler(TaskAsyncParam asyncParam, Object beforeResult) {
		return super.handler(asyncParam, beforeResult);
	}

	@Override
	protected final Object afterHandler(TaskAsyncParam asyncParam, TaskAsyncResultTO taskAsyncResult) {
		return super.afterHandler(asyncParam, taskAsyncResult);
	}
}
